// NOTE:this file is client hot module reload.

const vueMap = Object.create(null);
window.__VUE_HOT_MAP__ = vueMap; // to debug and for other vue tools to detect it

// for update function
export function vueHotLoad(id, options) {
  if (!vueMap[id]) {
    recordVue(id, options);
  } else {
    vueLoad(id, options);
  }
}

// TODO: record vue instance when vue created
export function recordVue(id, options) {
  function beforeCreate() {
    if (!vueMap[id]) {
      vueMap[id] = {
        Ctor: this.constructor,
        instances: [this],
        options,
      };
    } else {
      const record = vueMap[id];
      record.instances.push(this);
    }
  }
  function beforeDestroy() {
    if (vueMap[id]) {
      const instances = vueMap[id].instances;
      instances.splice(instances.indexOf(this), 1);
    }
  }
  options.beforeCreate = options.beforeCreate
    ? Array.isArray(options.beforeCreate)
      ? options.beforeCreate.concat(beforeCreate)
      : [options.beforeCreate, beforeCreate]
    : beforeCreate;
  options.beforeDestroy = options.beforeDestroy
    ? Array.isArray(options.beforeDestroy)
      ? options.beforeDestroy.concat(beforeDestroy)
      : [options.beforeDestroy, beforeDestroy]
    : beforeDestroy;
}

// NOTE: this function is modified by https://github.com/vuejs/vue-hot-reload-api/blob/master/src/index.js L216,
// I cannot make it sure enough that could use at production environment.
// use it cautious
export function vueLoad(id, options) {
  if (!vueMap[id]) {
    return console.warn(`no such id:${id}`);
  }
  const record = vueMap[id];
  if (options) {
    recordVue(id, options);
    if (typeof options === "function") {
      options = options.options;
    }
    if (record.Ctor) {
      const newCtor = record.Ctor.super.extend(options);
      newCtor.options._Ctor = record.options._Ctor;
      record.Ctor.options = newCtor.options;
      record.Ctor.cid = newCtor.cid;
      record.Ctor.prototype = newCtor.prototype;
      if (newCtor.release) {
        // temporary global mixin strategy used in < 2.0.0-alpha.6
        newCtor.release();
      }
    } else {
    }
  }
  record.instances.slice().forEach((instance) => {
    // FIXME: debug for update
    if (options.render === undefined) {
      instance.$options.render = options.render;
      instance.$options.staticRenderFns = options.staticRenderFns;
      // reset static trees
      // pre 2.5, all static trees are cached together on the instance
      if (instance._staticTrees) {
        instance._staticTrees = [];
      }
      // 2.5.0
      if (Array.isArray(record.Ctor.options.cached)) {
        record.Ctor.options.cached = [];
      }
      // 2.5.3
      if (Array.isArray(instance.$options.cached)) {
        instance.$options.cached = [];
      }

      // post 2.5.4: v-once trees are cached on instance._staticTrees.
      // Pure static trees are cached on the staticRenderFns array
      // (both already reset above)

      // 2.6: temporarily mark rendered scoped slots as unstable so that
      // child components can be forced to update
      const restore = patchScopedSlots(instance);
      instance.$forceUpdate();
      instance.$nextTick(restore);
    }
    if (instance.$vnode && instance.$vnode.context) {
      instance.$vnode.context.$forceUpdate();
    } else {
      console.warn(
        "Root or manually mounted instance modified. Full reload required."
      );
    }
  });
}

// 2.6 optimizes template-compiled scoped slots and skips updates if child
// only uses scoped slots. We need to patch the scoped slots resolving helper
// to temporarily mark all scoped slots as unstable in order to force child
// updates.
function patchScopedSlots(instance) {
  if (!instance._u) return;
  // https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.js
  const original = instance._u;
  instance._u = (slots) => {
    try {
      // 2.6.4 ~ 2.6.6
      return original(slots, true);
    } catch (e) {
      // 2.5 / >= 2.6.7
      return original(slots, null, true);
    }
  };
  return () => {
    instance._u = original;
  };
}
